const PromiseQueue = require("promise-queue");
const WrapWebtorrent = require("wrap-webtorrent");
const path = require("path");
const process = require("process");
const EventEmitter = require("events").EventEmitter;
const Debug = require("debug");
const uuid = require("uuid");
const filenamify = require("filenamify");
const jsonBeautify = require("json-beautify");
const buffer = require("buffer");
const runParallel = require("run-parallel-limit");
const cwd = process.cwd();
const Worktile = require("lib-worktile/lib").web.WorktileWeb;
const lib_getTasks = require("./get_tasks");
const lib_archive = require("./archive");
const Toolbox = require("./Toolbox");
const lib_upload = require("./upload");
const lib_comment = require("./comment");
const lib_ecodingUpload = require("./e.coding");
const del = require("del");
const USERNAME = require("./username").USERNAME;

const STATES_IDS = {
  DOWNLOADING: "5db418bbd883940d84cac9dc",
  TASK_FAILED: "5db418cd59b6200d66279682",
  "TASK_SUCCESS": "5db4190b59b6200d66279685"
};
const PROPERTY_IDS = {
  "OWHAT": "5db4205c59b6200d662796c9",
  "TUNIU_WANGSU": "5db420443d052410605f8f29",
  "TUNIU_QINIU": "5db4215ebf760b1042d528e8",
  "MAFENGWO": "5db420036fbcac100665e38d",
  "GITHUB": "5db42072d883940d84caca6a",
  "JIANLIAO": "5c1e3baae0a66f14600883f2????",
  "PIANKE_7NIU": "5db42017d883940d84caca45",
  "EASTDAY": "5c1e3b5875916b13f3cdf536????",
  "SOBOT": "5c1e3c432bffa613d46e8e1b",
  "WPS": "5c1e3c5875916b13f3cdf53a",
  "DAO_VOICE": "5db4210b2e8c4066ff874955",
  "HUNLIJI": "5db4212a2e8c4066ff874958",
  "END_TIME": "5db31027f000a7355cf418bd",
  "_7MOOR": "5db4213fd883940d84caca75",
  "UDESK": "5db421b659b6200d662796d2",
  "MEIPIAN": "5db421997a322b0da2ec4193",
  "HIAITA": "5db421d159b6200d662796d4",
  "DúAPP": "5db4226a6fbcac100665e397",
  "BABYTREE": "5db421ee3d052410605f8f37",
  "E.CODING": "5db4228f59b6200d662796ed",
  "WTBOX": "5db422b77a322b0da2ec419a"
}

class DownloaderForNyaaFapPictures extends EventEmitter {
  /**
   * 
   * @param {String} view_id 
   * @param {Number} max_downloading 
   * @param {Number} max_uploading 
   * @param {Boolean} debug_mode 
   * @param {Number} default_page 
   */
  constructor(view_id, max_downloading = 5, max_uploading = 3, debug_mode = false, default_page = 0) {
    super();
    this.view_id = view_id;
    max_downloading = parseInt(max_downloading);
    max_uploading = parseInt(max_uploading);
    default_page = parseInt(default_page);
    if (max_downloading <= 0) max_downloading = 1;
    if (max_uploading <= 0) max_uploading = 1;
    if (default_page < 0) default_page = 0;
    this.max_downloading = max_downloading;
    this.max_uploading = max_uploading;
    this.debug_mode = debug_mode;
    this.default_page = default_page;
    this.upload_queue = new PromiseQueue(max_uploading, Infinity);
    this.tar_queue = new PromiseQueue(1, Infinity);
    this.fetch_task_queue = new PromiseQueue(1, Infinity);
    let downloader = new WrapWebtorrent.Downloader(path.join(cwd, "./mnt", "camo_db"), this.max_downloading);
    this.torrent_downloader = downloader;
    this.d_for_add_tasks = Debug("<fetch tasks>");
    this.d_for_add_tasks.enabled = true;
    this.__bind_downloader_events();
    if (!debug_mode) {
      this.__loop_add_tasks();
    }
  }

  /**
   * @private
   */
  __bind_downloader_events() {
    let downloader = this.torrent_downloader;
    downloader.on("task-speed", (info) => {
      // debugger
      let title = info.task.extraInfo.task_desc_info.title;
      let torrent_id = info.task.torrentId;
      // console.log(torrent_id, title, info.reason);
    });
    downloader.on("task-fail", async (info) => {
      // debugger
      let torrent_id = info.task.torrentId;
      let task_worktile_id = info.task.extraInfo.worktile_id;
      let task_title = info.task.extraInfo.task_desc_info.title;
      console.log(torrent_id, task_title, "download failed!");
      let o_wtInstance = await Worktile.getValidWorktileInstance("majo", "ktxp@outlook.com", "a123456789");
      if (!o_wtInstance.ok) {
        let err = `(in downloader.on("task-fail") )login worktile failed:${o_wtInstance.msg}`;
        this.emit("warning", err);
        return;
      }
      let wt = o_wtInstance.instance;
      let o_changeState = await wt.changeTaskState(task_worktile_id, STATES_IDS.TASK_FAILED);
      if (!o_changeState.ok) {
        this.emit("warning", `试图更改${task_title}|${torrent_id}的状态为下载失败,但是:${o_changeState.msg}`);
      }
      let o_comment = await lib_comment.attachmentComment(wt, task_worktile_id,
        `下载失败_${task_title}:${info.reason}`, `下载失败_${task_title}.txt`);
      let o_changeEndTime = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS.END_TIME, {
        date: parseInt(Date.now() / 1000),
        with_time: true
      });
      if (!o_changeEndTime.ok) {
        this.emit("warning", `试图更改截止时间失败:${o_changeEndTime.msg}`)
      }
      del(info.task.torrentStoreDir).catch(err => { })
    });
    downloader.on("task-done", async info => {
      // debugger
      let title = info.task.extraInfo.task_desc_info.title;
      let torrent_id = info.task.torrentId;
      let task_worktile_id = info.task.extraInfo.worktile_id;
      let is_a_lot_of_pictures = info.task.extraInfo.a_lot_of_pictures;
      let my_uuid = info.task.extraInfo.torrent_uuid;
      let store_dir = info.task.torrentStoreDir;
      /**@todo 如果是图片密集需要进行打包 */
      if (is_a_lot_of_pictures) {
        this.tar_queue.add(() => {
          return new Promise(async resolve => {
            let o_arc = await lib_archive.tarDir(store_dir, path.join(cwd, "mnt", "arc", `${my_uuid}`, `${filenamify(title)}.tar`));
            if (o_arc.ok) {
              this.upload_queue.add(() => this.__upload_dir(my_uuid, torrent_id, title, task_worktile_id, true));
            } else {
              this.emit("warning", `error when trying tar ${title}:${o_arc.msg}`);
              this.upload_queue.add(() => this.__upload_dir(my_uuid, torrent_id, title, task_worktile_id, false));
            }
            setTimeout(() => resolve(), 2000);
          })
        });
      } else {
        this.upload_queue.add(() => this.__upload_dir(my_uuid, torrent_id, title, task_worktile_id, false));
      }
    })
  }


  /**
   * @description 上传 评论 改属性 删除文件操作等
   * @returns {Promise<{ok:Boolean,msg:String}>}
   * @param {String} my_uuid 用uuid可以推断出,下载文件和打包文件的位置
   * @param {String} task_worktile_id 
   * @param {String} torrent_id
   * @param {Boolean} is_tared 
   * @param {String} task_title
   */
  __upload_dir(my_uuid, torrent_id, task_title, task_worktile_id, is_tared) {
    return new Promise(async resolve => {
      let o_wtInstance = await Worktile.getValidWorktileInstance("majo", "ktxp@outlook.com", "a123456789");
      if (!o_wtInstance.ok) {
        let err = `(in __upload_dir() )login worktile failed:${o_wtInstance.msg}`;
        this.emit("warning", err);
        return resolve({
          ok: false,
          msg: err
        })
      }
      let wt = o_wtInstance.instance;
      (async comment_下载成功 => {
        let message = `下载成功,位于${USERNAME}的硬盘目录:(${path.join(cwd, "mnt", "down", my_uuid)})`;
        let o_comment = await wt.commentTask(task_worktile_id, message);
        if (!o_comment.ok) {
          this.emit("warning", `评论${task_title}失败,内容为[${message}],失败原因为${o_comment.msg}`)
        }
      })();
      let path_to_upload = is_tared ? path.join(cwd, "mnt", "arc", my_uuid) : path.join(cwd, "mnt", "down", my_uuid);


      let task_tuniu_wangsu = async cb => {
        console.log(torrent_id, "upload tuniu ...");
        let o_up = await lib_upload.uploadDirToTuniu(path_to_upload, is_tared);
        let o_comment = await lib_comment.attachmentComment(wt, task_worktile_id, o_up.data.html, `上传tuniu.html`)
        if (!o_comment.ok) {
          this.emit("warning", `comment ${task_title} 失败:${o_comment.msg}`)
        }
        if (o_up.ok) {
          lib_comment.attachmentComment(wt, task_worktile_id,
            `\`\`\`json\n${jsonBeautify({ drive: "tuniu", result: o_up }, null, 2, 20)} \n\`\`\``,
            `${task_title} - tuniu.json`);
          let p_changeProperty = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS.TUNIU_WANGSU, o_up.data.html);
          return cb(null, {
            upload_ok: p_changeProperty.ok
          })
        } else {
          this.emit(`warning`, `上传${task_title}到tuniu失败:[${o_up.errors.join(" \n|| ")}]`);
          return cb(null, {
            upload_ok: false
          })
        }
      };

      let task_ecoding = async cb => {
        console.log(torrent_id, "upload ecoding..");
        let o_up = await lib_ecodingUpload.ecodingBackup(path_to_upload);
        if (!o_up.ok) {
          let o_comment_fail = await lib_comment.attachmentComment(wt, task_worktile_id, o_up.msg, `上传E.coding失败[${task_title}].txt`);
          if (!o_comment_fail.ok) {
            this.emit("warning", `comment ${task_title} 失败:${o_comment.msg}`)
          }
          return cb(null, {
            upload_ok: false
          })
        }
        await lib_comment.attachmentComment(wt, task_worktile_id, `\`\`\`json\n${jsonBeautify({ drive: "e.coding-net", result: o_up }, null, 2, 20)} \n\`\`\``,
          `${task_title} - e.coding-net.json`);
        let o_change = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS['E.CODING'], o_up.data.markdown);
        cb(null, {
          upload_ok: true
        })
      }

      let task_qiniu_universally = async cb => {
        console.log(torrent_id, "upload qiniu..");
        let o_tooManySmallFiles = await Toolbox.checkTooManySmallFiles(path_to_upload);
        let o_up = await lib_upload.uploadDirToQiniuUniversally(path_to_upload, undefined, undefined, undefined,
          !o_tooManySmallFiles);
        if (!o_up.ok) {
          let o_comment_fail = await lib_comment.attachmentComment(wt, task_worktile_id, o_up.msg, `上传七牛通用失败[${task_title}].txt`);
          if (!o_comment_fail.ok) {
            this.emit("warning", `comment ${task_title} 失败:${o_comment.msg}`)
          }
          return cb(null, {
            upload_ok: false
          })
        }
        lib_comment.attachmentComment(wt, task_worktile_id, `\`\`\`json\n${jsonBeautify({ drive: "qiniu_universally", result: o_up }, null, 2, 20)} \n\`\`\``,
          `${task_title} - QiniuUniversally.json`);
        lib_comment.attachmentComment(wt, task_worktile_id, `\n${
          ($ => {
            let html = new buffer.Buffer("");
            for (let h in o_up.data) {
              html += (o_up.data[h].toHTML());
            }
            return html.toString();
          })()}`,
          `${task_title} - QiniuUniversally.html`);
        let p_changeOwhat = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS.OWHAT, o_up.data.owhat.toHTML());
        // debugger
        let p_change7moor = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS._7MOOR, o_up.data._7moor.toHTML());
        let p_changeHunliji = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS.HUNLIJI, o_up.data.hunliji.toHTML());
        let p_changePianke7niu = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS.PIANKE_7NIU, o_up.data.pianke.toHTML());
        let p_changeDaovoice = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS.DAO_VOICE, o_up.data.daovoice.toHTML());
        let p_changeTuniuQiniu = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS.TUNIU_QINIU, o_up.data.tuniu.toHTML());
        let p_changeUdesk = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS.UDESK, o_up.data.udesk.toHTML());
        let p_changeMeipian = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS.MEIPIAN, o_up.data.meipian.toHTML());
        let p_changeHiaita = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS.HIAITA, o_up.data.hiaita.toHTML());
        let p_changeDúapp = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS.DúAPP, o_up.data.dúapp.toHTML());
        let p_changeBabytree = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS.BABYTREE, o_up.data.babytree.toHTML())
        return cb(null, {
          upload_ok: [
            p_change7moor,
            p_changeOwhat,
            p_changeHunliji,
            p_changePianke7niu,
            p_changeDúapp,
            p_changeTuniuQiniu
          ].filter(o => o.ok).length >= 3
        })

      }

      let todo_tasks = [
        // task_mafengwo,
        // task_hunliji,
        // task_pianke7niu,
        // task_owhat,
        // task_tuniu_wangsu,
        task_qiniu_universally,
        task_ecoding
      ];
      /**@type {Promise<{okcount:Number}>} */
      let pr = new Promise(upload_finish => {
        runParallel(todo_tasks, 1, (err, results) => {
          let okcount = results.filter(o => o['upload_ok']).length;
          upload_finish({
            okcount: okcount
          })
        })
      });
      pr.then(async o => {
        if (o.okcount >= 1) {
          let o_changeState = await wt.changeTaskState(task_worktile_id, STATES_IDS.TASK_SUCCESS);
          if (!o_changeState.ok) {
            this.emit("warning", `试图改变${task_title}的状态为任务成功,失败了,原因是:${o_changeState.msg}`);
          } else {
            this.torrent_downloader.emit("task-delete", torrent_id);
          }
          del(path.join(cwd, "mnt", "arc", my_uuid)).catch(err => { });

          // del(path.join(cwd, "mnt", "down", my_uuid)).catch(err => { });
          resolve({
            ok: true,
            msg: "???"
          });
          let o_changeEndTime = await wt.changeTaskProperty(task_worktile_id, PROPERTY_IDS.END_TIME, {
            date: parseInt(Date.now() / 1000),
            with_time: true
          });
          if (!o_changeEndTime.ok) {
            this.emit("warning", `试图更改截止时间失败:${o_changeEndTime.msg}`)
          }

        } else {
          let err = `上传${task_title}的成功载点少于2个`;
          this.emit("warning", err);
          resolve({
            ok: false,
            msg: err
          })
        }
      })

    })
  }

  /**
   * @private
   */
  __loop_add_tasks() {
    this.fetch_task_queue.add(
      /**
       * @returns {Promise<{ok:Boolean,msg:String}>}
       */
      () => {
        let p = new Promise(async resolve => {
          let o_getDbTasksCount = await this.torrent_downloader.getDbTaskCount();
          if (!o_getDbTasksCount.ok) {
            return resolve({
              ok: false,
              msg: `error trying get dbTaskCount:${o_getDbTasksCount.msg}`
            })
          }
          this.d_for_add_tasks(`%d tasks are being downloading`, o_getDbTasksCount.count);
          if (o_getDbTasksCount.count >= this.max_downloading) {
            this.d_for_add_tasks('o_getDbTasksCount.count >= this.max_downloading')
            return resolve({
              ok: true,
              msg: `o_getDbTasksCount.count >= this.max_downloading`
            })
          }
          let o_wtInstance = await Worktile.getValidWorktileInstance("majo", "ktxp@outlook.com", "a123456789");
          if (!o_wtInstance.ok) {
            return resolve({
              ok: false,
              msg: `failed to login worktile:${o_wtInstance.msg}`
            })
          }
          let wt = o_wtInstance.instance;
          let o_getTasks = await lib_getTasks.getTasksByViewID(wt, this.view_id, this.default_page);
          if (!o_getTasks.ok) {
            return resolve({
              ok: false,
              msg: `failed when lib_getTasks.getTasksByViewID:${o_getTasks.msg}`
            })
          }
          for (let task of o_getTasks.data.value.slice(0, this.max_downloading - o_getDbTasksCount.count)) {
            let o_getTaskInfo = await wt.getTaskInfoPro(task._id);
            if (!o_getTaskInfo.ok) {
              this.emit("warning", `error when get detail task info of [${task.title}:${task._id}]:${o_getTaskInfo.msg}`);
              continue;
            }
            let desc = o_getTaskInfo.data.value.properties.desc.value;
            let tag_ids = o_getTaskInfo.data.value.properties.tag.value;
            let a_lot_of_pictures = tag_ids.includes('5c2fdc44d59d78524e0905f0');
            try {
              let task_desc_info = JSON.parse(desc);
              let o_switchState = await wt.changeTaskState(task._id, STATES_IDS.DOWNLOADING);
              if (!o_switchState.ok) {
                this.emit(`warning`, `error when trying change state of ${task.title} to DOWNLOADING:${o_switchState.msg}`);
                continue;
              }
              let torrent_uuid = uuid.v4();
              let torrent_link = task_desc_info.torrent;
              if(torrent_link.includes("https://sukebei.nyaa.si")){
                torrent_link = torrent_link.replace("sukebei.nyaa.si","s.nyaa.liblu.me")
              }
              if(torrent_link.includes("https://nyaa.si")){
                torrent_link = torrent_link.replace("nyaa.si","nyaa.liblu.me")
              }
              let o_addToTorrentDownloader = await this.torrent_downloader.addTask(torrent_link, path.join(
                cwd, "mnt", "down", torrent_uuid
              ), {
                worktile_id: task._id,
                a_lot_of_pictures: a_lot_of_pictures,
                task_desc_info: task_desc_info,
                torrent_uuid: torrent_uuid
              }
              );
              if (!o_addToTorrentDownloader.added_to_queue) {
                this.emit("warning", `failed to add task(${task.title}) to  download queue:${o_addToTorrentDownloader.msg}`);
                continue;
              }
              let o_comment = await wt.commentTask(task._id, `领取by${USERNAME}`);
              if (!o_comment.ok) {
                this.emit(`warning`, `试图评论task(${task.title}),内容为领取by${USERNAME},失败原因:${o_comment.msg}`)
              }
            } catch (json_error) {
              this.emit("warning", `error trying parse desc json:${desc}`);
              continue;
            }
          }
          resolve({
            ok: true,
            msg: "ok"
          });
        });

        p.then(o => {
          // debugger
          if (!o.ok) {
            this.emit("warning", `this.__loop_add_tasks failed:${o.msg}`)
          }
          setTimeout(() => this.__loop_add_tasks(), 5000);
        });

        return p;
      })
  }
}



module.exports = {
  Downloader: DownloaderForNyaaFapPictures
}